﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TT.Logic.Entities;

namespace TT.Logic.Tournament
{
    public class CupGenerator : Interfaces.ITournamentGenerator
    {
        private readonly Interfaces.ITTContext _dbContext;
        private int TotalPlayers { get; set; }        

        public CupGenerator(Interfaces.ITTContext context) {
            _dbContext = context;
        }

        /// <summary>
        /// Generates tournament
        /// </summary>
        public Entities.Tournament CreateTournament(ICollection<Entities.Player> players) {
            var tournament = new Entities.Tournament();
            tournament.TournamentDate = DateTime.Now;
            tournament.Rounds = new List<Entities.Round>();

            TotalPlayers = players.Count;
            var randomTeamColl = players;

            int teamsInFirstRound = NumberOfGamesInFirstRound(TotalPlayers);
            int teamsWithByeCount = 0;

            IEnumerable<Player> teamsForFirstRound = randomTeamColl.Take(teamsInFirstRound);
            int teamsThatGotBye = randomTeamColl.Count - teamsInFirstRound;

            IEnumerable<Player> teamsWithBye = randomTeamColl.Skip(teamsInFirstRound).Take(teamsThatGotBye);
            IEnumerator<Player> enumrator = teamsForFirstRound.GetEnumerator();

            Dictionary<int, List<Match>> games =
                new Dictionary<int, List<Match>>();
            Guid nextCupId = Guid.NewGuid();
            
            while (enumrator.MoveNext())
            {
                Player teamOne = enumrator.Current;
                enumrator.MoveNext();
                Player teamTwo = enumrator.Current;
                //add to 1st round
                var game = new Match()
                {
                    HomePlayer1 = teamOne,
                    AwayPlayer1 = teamTwo
                };

                if (games.ContainsKey(0))
                {
                    games[0].Add(game);
                }
                else
                {
                    games.Add(0, new List<Match>() { game });
                }
            }

            int roundCounter = 1;
            if (_usePreliminaryRound)
            {

                int numberOfTeamsAfterFirstRound = teamsInFirstRound / 2;
                // number of teams that will go to 2nd round = number of round in round 1  + byes from round 1

                IEnumerator<Player> byeEnumrator = teamsWithBye.GetEnumerator();
                
                while (byeEnumrator.MoveNext())
                {
                    teamsWithByeCount++;

                    Player teamOne = byeEnumrator.Current;
                    Player teamTwo = null;

                    if (byeEnumrator.MoveNext())
                    {
                        teamsWithByeCount++;
                        teamTwo = byeEnumrator.Current;
                    }
                    //insert "byes" if any
                    //they are goin directly to round 2 !
                    var game = new Match()
                    {
                        HomePlayer1 = teamOne,
                        AwayPlayer1 = teamTwo

                    };
                    if (games.ContainsKey(1))
                    {
                        games[1].Add(game);
                    }
                    else
                    {
                        games.Add(1, new List<Match>() { game });
                    }
                }
                int counter = randomTeamColl.Count - teamsWithByeCount - numberOfTeamsAfterFirstRound;
                if (counter % 2 == 1)
                {
                    counter--;
                }

                for (int i = 0; i < 
                    counter;
                    //teamsWithByeCount - teamsInFirstRound;
                    i += 2)
                {
                    //add other teams/players here to round 2 !!
                    var game = new Match()
                    {
                        //null vs. null
                    };
                    if (games.ContainsKey(1))
                    {
                        games[1].Add(game);
                    }
                    else
                    {
                        games.Add(1, new List<Match>() { game });
                    }
                }
                roundCounter = 2;

            }
            int numberOfRoundsToGenerate = RoundToGenerate(TotalPlayers);
            //starting from last generated round add all required rounds.
            for (int i = roundCounter; i < numberOfRoundsToGenerate; i++)
            {

                int teamsInRound = NumberOfTeamsRequiredToPlayIn(i);
                    //_cupHelper.NumberOfTeamsRequiredToPlayIn(i);
                if (!_usePreliminaryRound)
                {
                    teamsInRound = NumberOfTeamsRequiredToPlayIn(i + 1);
                }

                for (int j = 0; j < teamsInRound; j += 2)
                {
                    Player teamOne = null;
                    Player teamTwo = null;

                    var game = new Match();
                    if (games.ContainsKey(i))
                    {
                        games[i].Add(game);
                    }
                    else
                    {
                        games.Add(i, new List<Match>() { game });
                    }
                }

            }

            foreach (var round in games)
            {
                int matchInRoundNumber = 0;
                var roundToAdd = new Round()
                {
                    //assign round number...
                    RoundNumber = round.Key,
                    //...and ref back to tournament
                    Tournament = tournament
                };
                roundToAdd.Matches = new List<Match>();
                tournament.Rounds.Add(roundToAdd);
                foreach (var match in round.Value)
                {
                    match.Round = roundToAdd;
                    //assign index of match within the round
                    match.MatchInRoundNumber = matchInRoundNumber;
                    roundToAdd.Matches.Add(match);
                    ++matchInRoundNumber;
                }
            }
            
            //save to DB
            foreach (var round in tournament.Rounds)
            {
                foreach (var match in round.Matches)
                {
                    _dbContext.Matches.Add(match);
                }
                _dbContext.Rounds.Add(round);
            }
            _dbContext.Tournaments.Add(tournament);
            _dbContext.SaveChanges();

            return tournament;
        }

        /// <summary>
        /// Progress winner of the match to the next match in tournament.
        /// </summary>
        public ProgressResult ProgressWinnerToNextRound(Match match, Logic.Entities.Tournament tournament)
        {

            var toReturn = new ProgressResult();

            //get winner
            var playerToProgress = match.GetWinner();
            var nextRoundNumber = match.Round.RoundNumber + 1;
            var nextRound = 
                tournament.Rounds.FirstOrDefault(x => x.RoundNumber == nextRoundNumber);

            var totalPlayers = (from r in tournament.Rounds
                               from m in r.Matches
                               where m.HomePlayer1!=null ||
                               m.AwayPlayer1!=null
                               select 
                               new {m.HomePlayer1, m.AwayPlayer1}).ToList();
            
            HashSet<int> playerIds = new HashSet<int>();
            foreach (var item in totalPlayers)
	        {
		        if(item.AwayPlayer1!=null &&
                    !playerIds.Contains(item.AwayPlayer1.Id)){
                    playerIds.Add(item.AwayPlayer1.Id);
                }
                if(item.HomePlayer1!=null &&
                    !playerIds.Contains(item.HomePlayer1.Id)){
                    playerIds.Add(item.HomePlayer1.Id);
                }
            }

            var nextMatchInRoundNumber = GeNextMatch(match, playerIds.Count);
            if (!nextMatchInRoundNumber.HasValue) {
                return null;
            }
            var nextMatch = (from m in _dbContext.Matches
                            where m.Round.Tournament.Id == tournament.Id &&
                            m.MatchInRoundNumber == nextMatchInRoundNumber &&
                            m.Round.RoundNumber == nextRoundNumber
                            select m).FirstOrDefault();
            if (nextMatch != null)
            {
               // foreach (var nextMatch in nextRound.Matches)
                {
                    bool progressed = false;
                    if (nextMatch.HomePlayer1 == null)
                    {
                        toReturn.IsHome = true;
                        nextMatch.HomePlayer1 = playerToProgress;
                        progressed = true;
                    }
                    else if (nextMatch.AwayPlayer1 == null)
                    {
                        toReturn.IsHome = false;
                        nextMatch.AwayPlayer1 = playerToProgress;
                        progressed = true;
                    }
                    if (progressed)
                    {
                        toReturn.MatchId = nextMatch.Id;
                        toReturn.PlayerFirstName = playerToProgress.FirstName;
                    }
                }
                
            }
            _dbContext.SaveChanges();
            return toReturn;

        }

        /// <summary>
        /// Returns number of rounds to be generated for players
        /// </summary>        
        private int RoundToGenerate(int totalPlayers) {
            return (int)Math.Ceiling(Math.Log(totalPlayers, 2));
        }

        /// <summary>
        /// Returns boolean value indicating whether input parameter is power of 2
        /// </summary>
        bool IsPowerOfTwo(int x)
        {
            var result = (x & (x - 1)) == 0;            
            return result;
        }

        private bool _usePreliminaryRound=false;
        /// <summary>
        /// Returns number of games required in 1st round
        /// </summary>
        private int NumberOfGamesInFirstRound(int totalPlayers) {
            //if we have number players that is power of 2 then we dont have any byes
            if (IsPowerOfTwo(totalPlayers))
            {                
                return totalPlayers;
            }           
            
            _usePreliminaryRound = true;                
            return (totalPlayers - 4) * 2;
            
        }

        /// <summary>
        /// Returns number of players required to play in <param name="roundNumber"></param>
        /// </summary>
        public int NumberOfGamesInRound(int totalPlayers, int roundNumber)
        {            
            if (roundNumber == 0) {
                throw new ArgumentException("Round numbers should start from 1.");
            }

            var byes =GetByes(totalPlayers);
            return
                    ((NumberOfGamesInFirstRound(totalPlayers)
                    / (roundNumber)) + byes) / 2;       
            
        }

        public int GetByes(int totalPlayers)
        {
            var firstRoundGames = NumberOfGamesInFirstRound(totalPlayers);
            return totalPlayers - firstRoundGames;
        }


        public int NumberOfTeamsRequiredToPlayIn(int roundNumber)
        {
            var firstRoundGames = NumberOfGamesInFirstRound(TotalPlayers);

            if (_usePreliminaryRound)
            {
                if (roundNumber == 1)
                {
                    return firstRoundGames;
                }

                int firstRoundByes = TotalPlayers - firstRoundGames;
                int secondRoundTeams = (firstRoundGames / 2) + firstRoundByes;
                return secondRoundTeams / (roundNumber);
            }
            else
            {
                if (roundNumber == 1)
                {
                    return firstRoundGames;
                }

                return firstRoundGames / (roundNumber);
            }

        }


        /// <summary>
        /// Returns next match for the current match, tournament and total players
        /// </summary>        
        public int? GeNextMatch(Logic.Entities.Match match,             
            int totalPlayers)
        {
            if (match.Round == null)
            {
                throw new Exception("Round needs to be loaded before using this method");
            }

            var totalRounds = RoundToGenerate(totalPlayers);
            if (match.Round.RoundNumber + 1 >= totalRounds) {
                //return null if current match is FINAL or non-existing match
                return null;
            }

            var matchesWithByes = GetByes(totalPlayers) / 2;
            int byes = (int)Math.Floor((decimal)matchesWithByes);
            int roundNumber = match.Round.RoundNumber;

            if (match.MatchInRoundNumber == 0 || match.MatchInRoundNumber == 1)
            {
                int nextMatchInRound = 0;
                if (roundNumber == 0) {
                    //if current match is match from first round, we need to take byes into account
                    nextMatchInRound += byes;
                }
                return nextMatchInRound;
            }
            else
            {
                if (match.MatchInRoundNumber % 2 == 0)
                {
                    return (match.MatchInRoundNumber / 2);
                }
                else
                {
                    return ((match.MatchInRoundNumber - 1) / 2);
                }
            }
        }
     
    }
}
